using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Runtime.Serialization;
using needle.Services.astra.Common;

namespace needle.Services.astra.Collections
{
	/// <summary>
	/// Document with its <see cref="M:Data"/>.
	/// </summary>
	/// <typeparam name="T">Type of <see cref="M:Data"/>.</typeparam>
	[Serializable]
	public class Document<T> : IDocument, IParent<Documents>, INotifyPropertyChanged, ISerializable
	{
		/// <summary>
		/// Constructor.
		/// <see cref="IsChanged"/> is kept on false.
		/// </summary>
		/// <param name="data">See <see cref="Data"/>.</param>
		public Document(T data)
		{
			_data = data;
		}
		
		/// <summary>
		/// Constructor for deserialization.
		/// </summary>
		protected Document(SerializationInfo info, StreamingContext context)
		{
			_data = (T)info.GetValue("_data", typeof(T));
		}

		/// <summary>
		/// Uri of this document.
		/// <seealso cref="IsNew"/>.
		/// </summary>
		/// <value>Default: null.</value>
		public Uri Uri
		{
			get { return _uri; }
			set
			{
				if (_uri == value)
					return;
				_uri = value;
				OnPropertyChanged("Uri");
				OnPropertyChanged("IsNew");
				OnPropertyChanged("Name");
			}
		}
		/// <summary>
		/// Document name.
		/// </summary>
		/// <value>
		/// If <see cref="Parent"/> is not null, returns <see cref="Documents.GetName"/>, otherwise null.
		/// </value>
		public virtual string Name
		{
			get
			{
				if (this.Parent == null)
					return null;
				else
					return this.Parent.GetName(this);
			}
		}
		/// <summary>
		/// Whether this document is open as read only.
		/// </summary>
		/// <value>Default: false.</value>
		public bool IsReadOnly
		{
			get { return _isReadOnly; }
			set
			{
				if (_isReadOnly == value)
					return;
				_isReadOnly = value;
				OnPropertyChanged("IsReadOnly");
			}
		}
		/// <summary>
		/// Whether this document is new and not saved yet.
		/// </summary>
		/// <value>
		/// <see cref="Uri"/> is null.
		/// Default: true.
		/// </value>
		public bool IsNew
		{
			get { return this.Uri == null; }
		}
		/// <summary>
		/// Whether this document is changed and needs to be saved.
		/// See <see cref="Data"/> for more information.
		/// This can be also set manually.
		/// </summary>
		/// <value>Default: false.</value>
		public bool IsChanged
		{
			get { return _isChanged; }
			set
			{
				if (_isChanged == value)
					return;
				_isChanged = value;
				OnPropertyChanged("IsChanged");
			}
		}
		/// <summary>
		/// Document data.
		/// </summary>
		/// <remarks>
		/// If value implements <see cref="INotifyPropertyChanged"/> and the event occures, it is refired as "Data" property changed (which sets <see cref="IsChanged"/> to true) if <see cref="StorageHelper{T}.IsPropertyStorable(T, string)"/> is true.
		/// set: If changed, sets <see cref="IsChanged"/> to true.
		/// </remarks>
		public T Data
		{
			get
			{
				if (_data is INotifyPropertyChanged)
				{
					INotifyPropertyChanged npc = _data as INotifyPropertyChanged;
					npc.PropertyChanged -= Data_PropertyChanged;
					npc.PropertyChanged += Data_PropertyChanged;
				}
				return _data;
			}
			set
			{
				if (EqualityComparer<T>.Default.Equals(_data, value))
					return;
				_data = value;
				OnPropertyChanged("Data");
			}
		}

		#region IDocument Members

		/// <summary>
		/// See <see cref="Data"/>.
		/// </summary>
		object IDocument.Data
		{
			get
			{
				return this.Data;
			}
			set
			{
				this.Data = (T)value;
			}
		}

		#endregion

		#region IParent<Documents> Members

		/// <summary>
		/// Document parent - it is set by <see cref="Documents"/> when this document is added to the documents collection.
		/// </summary>
		/// <value>Default: null.</value>
		public Documents Parent
		{
			get { return _parent; }
			set
			{
				if (_parent == value)
					return;
				_parent = value;
				OnPropertyChanged("Parent");
				if (this.IsNew)
					OnPropertyChanged("Name");
			}
		}

		/// <summary>
		/// See <see cref="Parent"/>.
		/// </summary>
		[NonSerialized]
		private Documents _parent;

		#endregion

		#region INotifyPropertyChanged Members

		/// <summary>
		/// Fired when a property is changed.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Fires <see cref="PropertyChanged"/> if not null.
		/// </summary>
		/// <remarks>If <paramref name="propertyName"/> is "Data", <see cref="IsChanged"/> is set to true.</remarks>
		/// <param name="propertyName">Name of changed property.</param>
		protected virtual void OnPropertyChanged(string propertyName)
		{
			if (this.PropertyChanged != null)
				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
			if (propertyName == "Data")
				this.IsChanged = true;
		}

		#endregion

		#region ISerializable Members

		/// <summary>
		/// Serializes this object - stores <see cref="Data"/>.
		/// </summary>
		/// <param name="info"></param>
		/// <param name="context"></param>
		void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
		{
			info.AddValue("_data", _data, typeof(T));
		}

		#endregion

		/// <summary>
		/// Listens to <see cref="Data"/>.<see cref="INotifyPropertyChanged.PropertyChanged"/> if it has it and transforms it to <see cref="OnPropertyChanged"/> with "Data" if <see cref="StorageHelper{T}.IsPropertyStorable(T, string)"/> is true.
		/// </summary>
		private void Data_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (StorageHelper<T>.IsPropertyStorable((T)sender, e.PropertyName))
				OnPropertyChanged("Data");
		}

		/// <summary>
		/// See <see cref="Uri"/>.
		/// </summary>
		[NonSerialized]
		private Uri _uri;
		/// <summary>
		/// See <see cref="IsReadOnly"/>.
		/// </summary>
		[NonSerialized]
		private bool _isReadOnly;
		/// <summary>
		/// See <see cref="IsChanged"/>.
		/// </summary>
		[NonSerialized]
		private bool _isChanged;
		/// <summary>
		/// See <see cref="Data"/>.
		/// </summary>
		private T _data;
	}
}
